/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       window_clip_list.cpp

	$Header: /game/window_clip_list.cpp $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "window_clip_list.h"

#include "clip_list.h"
#include "game_window.h"

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
t_window_clip_list::t_window_clip_list() : m_extent(0,0,0,0)
{
}
  
// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
t_window_clip_list::t_window_clip_list( t_window_rect const& rect )
{
	m_rects.push_back( rect );
	m_extent = rect;
}


// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
void t_window_clip_list::clear()
{
	m_extent = t_screen_rect(0,0,0,0);
	m_rects.clear();
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
bool t_window_clip_list::contains( t_screen_rect const& rect ) const
{
	if (!m_extent.contains( rect ))
		return false;

	t_clip_list regions( rect );

	subtract( regions, *this );
	return regions.empty();
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
bool t_window_clip_list::intersects( t_screen_rect const& rect ) const
{
	if (!::intersect( m_extent, rect ))
		return false;

	t_const_iterator index = m_rects.begin();

	while (index != m_rects.end())
	{
		if (::intersect( *index, rect ))
			return true;
		index++;
	}
	return false;
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
void t_window_clip_list::offset( t_screen_point const& point )
{
	t_iterator index;

	for (index = m_rects.begin(); index != m_rects.end(); index++)
		*index += point;
	m_extent += point;
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
void t_window_clip_list::update_extent()
{
	if (m_rects.empty())
	{
		m_extent = t_screen_rect(0,0,0,0);
		return;
	}
	t_iterator index = m_rects.begin();

	m_extent = *index++;
	while (index != m_rects.end())
	{
		m_extent = ::get_extent( m_extent, *index++ );
	}
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
bool t_window_clip_list::remove( t_screen_rect const& rect )
{
	if (!::intersect( rect, m_extent ))
		return false;
	if (rect.contains( m_extent ))
	{
		m_rects.clear();
		return true;
	}

	t_iterator    index = m_rects.begin();
	t_iterator    last;
	t_screen_rect center;
	bool          changed = false;

	while (index != m_rects.end())
	{
		// find the intersection of the new rectangle and the one on the list
		center = intersection( rect, *index );
		// skip this rectangle if they don't overlap.
		if (center.height() <= 0 || center.width() <= 0)
			index++;
		else
		{
			changed = true;
			last = index;

			t_window_rect old_rect = *index++;   // make a copy of the old rectangle and remove it

			m_rects.erase( last );

			// determine what remains of the rectangle after we remove the new one.
			t_window_rect new_rect = old_rect;

			new_rect.bottom = center.top;
			if (new_rect.height() > 0)
				m_rects.push_front( new_rect );
			new_rect.top = center.top;
			new_rect.bottom = center.bottom;
			new_rect.right = center.left;
			if (new_rect.width() > 0)
				m_rects.push_front( new_rect );
			new_rect.left = center.right;
			new_rect.right = old_rect.right;
			if (new_rect.width() > 0)
				m_rects.push_front( new_rect );
			new_rect.left = old_rect.left;
			new_rect.right = old_rect.right;
			new_rect.bottom = old_rect.bottom;
			new_rect.top = center.bottom;
			if (new_rect.height() > 0)
				m_rects.push_front( new_rect );     
		}
	}
	return changed;
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
t_window_clip_list& t_window_clip_list::operator-=( t_screen_rect const& rect )
{
	if (remove( rect ))
		update_extent();
	return *this;
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
bool t_window_clip_list::remove( t_clip_list const& regions )
{
	if ( !::intersect( m_extent, regions.get_extent() ) )
		return false;

	t_clip_list::t_const_iterator index;
	bool                          changed = false;

	for (index = regions.begin(); index != regions.end(); index++)
	{
		if (remove( *index ))
		{
			changed = true;
			if (m_rects.empty())
				return true;
		}
	}
	return changed;
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
t_window_clip_list& t_window_clip_list::operator -= ( t_clip_list const& regions )
{
	if ( remove( regions ))
	{
		merge_rectangles();
		update_extent();
	}
	return *this;
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
void subtract( t_clip_list& clip_list, t_window_clip_list const& window_list )
{
	t_clip_list list_copy( window_list.begin(), window_list.end() );

	clip_list -= list_copy;
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
// check first rectangle in list to see if it can be merged with any
// later ones
void t_window_clip_list::merge_rectangles()
{
	if (m_rects.empty() || m_rects.front() == m_extent)
		return;

	t_iterator     index;

	while (true)
	{
		// find a rectangle which shares an edge with the current rectangle.
		index = m_rects.begin();
		t_window_rect& rect = *index++;

		while (index != m_rects.end())
		{
			// is it directly above or below?
			if (rect.left == index->left && rect.right == index->right)
			{
				if (rect.bottom == index->top && rect.windows == index->windows)
				{
					begin()->bottom = index->bottom;
					break;
				}
				if (rect.top == index->bottom && rect.windows == index->windows)
				{
					begin()->top = index->top;
					break;
				}
			}
			// is it directly left or right?
			if (rect.top == index->top && rect.bottom == index->bottom)
			{
				if (rect.right == index->left && rect.windows == index->windows)
				{
					begin()->right = index->right;
					break;
				}
				if (rect.left == index->right && rect.windows == index->windows)
				{
					begin()->left = index->left;
					break;
				}
			}
			index++;
		}
		// if we found and merged a rectangle, delete it.  Else stop.
		if (index != m_rects.end())
			m_rects.erase( index );
		else
			break;
	}
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
t_window_clip_list& t_window_clip_list::operator+=( t_window_rect const& rect )
{
	t_window_clip_list new_regions( rect );
	t_iterator         index;
	bool               should_merge = false;

	if (m_rects.empty())
	{
		m_extent = rect;
		m_rects.clear();
	}
	else
	{
		t_screen_rect center;

		if (::intersect( rect, m_extent ))
		{
			for (index = m_rects.begin(); index != m_rects.end(); index++)
			{
				center = intersection( rect, *index );
				if (center.width() <= 0 || center.height() <= 0)
					continue;

				t_window_rect new_rect( center );

				new_rect.windows.insert( new_rect.windows.end(), rect.windows.begin(), 
										 rect.windows.end() );
				new_rect.windows.insert( new_rect.windows.end(), index->windows.begin(),
										 index->windows.end() );
				new_regions.remove( center );
				new_regions.m_rects.push_back( new_rect );
			}
			remove( rect );
			should_merge = true;
		}
		m_extent = ::get_extent( m_extent, rect );
	}

	m_rects.insert( m_rects.begin(), new_regions.begin(), new_regions.end() );
	if (should_merge)
		merge_rectangles();
	return *this;
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
t_window_clip_list& t_window_clip_list::operator += ( t_window_clip_list const& regions )
{
	if (regions.empty())
		return *this;
	if (m_rects.empty())
	{
		*this = regions;
		return *this;
	}

	t_const_iterator index;

	for (index = regions.begin(); index != regions.end(); index++)
		*this += *index;

	return *this;
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
void t_window_clip_list::add( t_clip_list& regions, t_window* window )
{
	if (regions.empty())
		return;

	t_clip_list::t_const_iterator index;

	for (index = regions.begin(); index != regions.end(); index++)
		*this += t_window_rect( *index, window );
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
// remove all areas not within "rect"
void t_window_clip_list::intersect( t_screen_rect const& rect )
{
	// if the clip list is entirely within rect, stop.
	if (rect.contains( m_extent ))
		return;

	if (!::intersect( m_extent, rect ) )
	{
		clear();
		return;
	}

	t_iterator    index = m_rects.begin();
	t_iterator    last;
	t_screen_rect new_rect;

	while (index != m_rects.end())
	{
		new_rect = ::intersection( rect, *index );
		if (new_rect.width() <= 0 || new_rect.height() <= 0)
		{
			last = index++;   
			m_rects.erase( last );
		}
		else
		{
			index->left = new_rect.left;
			index->top = new_rect.top;
			index->right = new_rect.right;
			index->bottom = new_rect.bottom;
			index++;  
		}
	}
	update_extent();
}

// ---------------------------------------------------------------
// object to maintain a list of valid rectangles for drawing
// ---------------------------------------------------------------
void t_window_clip_list::intersect( t_clip_list const& regions )
{
	if (m_rects.empty())
		return;

	if (regions.empty() || !::intersect( m_extent, regions.get_extent() ))
	{
		clear();
		return;
	}

	if (*regions.begin() == regions.get_extent())
	{
		intersect( *regions.begin() );
		return;
	}

	std::list<t_window_rect> result_rects;

	t_iterator       index;
	t_clip_list::t_const_iterator source;
	t_screen_rect    result;
	t_screen_rect    rect;

	for (source = regions.begin(); source != regions.end(); source++)
	{
		rect = *source;
		for (index = m_rects.begin(); index != m_rects.end(); index++)
		{
			result = intersection( rect, *index );
			if (result.width() > 0 && result.height() > 0)
			{
				t_window_rect new_rect( result );
				
				new_rect.windows = index->windows;
				result_rects.push_back( new_rect );
			}
		}
	}
	m_rects = result_rects;
	merge_rectangles();
	update_extent();
}

